<html><head><title>Genealogy HTML</title></head><body>
<div style="float:left;margin-right:2em"><strong>People</strong><ul id="people"></ul></div>
<div id="person" style="float:left">
<table><tbody id="profile"></tbody></table>
<div style="float:left;margin-right:2em"><strong>Ancestors</strong><ul id="ancestors"></ul></div>
<div style="float:left;margin-right:2em">
<strong>Spouses</strong><ul id="spouses"></ul>
<strong>Siblings</strong><ul id="siblings"></ul>
<strong>Cousins</strong><ul id="cousins"></ul>
</div>
<div style="float:left"><strong>Descendants</strong><ul id="descendants"></ul></div>
</div>
<pre><script>
function show(id) {
	var person = Person.index[id];
	if (!person) {
		log('Invalid id ' + id);
		return;
	}
	
	var profile = document.getElementById('profile');
	removeAllChildren(profile);
	makeRow('Name', person, profile);
	makeRow('Born', person, profile);
	makeRow('Died', person, profile);
	
	var ancestors = document.getElementById('ancestors');
	removeAllChildren(ancestors);
	makeTreeLinks(person, ancestors, 'parents');
	
	var descendants = document.getElementById('descendants');
	removeAllChildren(descendants);
	makeTreeLinks(person, descendants, 'children');
	
	var spouses = document.getElementById('spouses');
	removeAllChildren(spouses);
	makeLinks(person.spouses, spouses);
	
	var siblings = document.getElementById('siblings');
	removeAllChildren(siblings);
	makeLinks(person.getSiblings(), siblings);
	
	var cousins = document.getElementById('cousins');
	removeAllChildren(cousins);
	makeLinks(person.getCousins(), cousins);
}

function makeLinks(people, ul) {
	for (var i = 0; i < people.length; i++) {
		makeItemLink(people[i], ul);
	}
}

function makeTreeLinks(person, ul, field) {
	for (var i = 0; i < person[field].length; i++) {
		var node = person[field][i];
		var li = makeItemLink(node, ul);
		if (node[field].length > 0) {
			var container = document.createElement('UL');
			li.appendChild(container);
			makeTreeLinks(node, container, field);
		}
	}
}

function makeRow(field, object, tbody) {
	if (!object[field]) return;
	var tr = document.createElement('TR');
	var th = document.createElement('TH');
	var td = document.createElement('TD');
	tr.appendChild(th);
	tr.appendChild(td);
	th.appendChild(document.createTextNode(field));
	td.appendChild(document.createTextNode(object[field]));
	tbody.appendChild(tr);
}

function removeAllChildren(node) {
	while (node.lastChild) {
		node.removeChild(node.lastChild);
	}
}

function makeItemLink(person, ul) {
	var li = document.createElement('LI');
	var a = document.createElement('A');
	a.href = 'javascript:show(' + person.id + ');';
	a.appendChild(document.createTextNode(person));
	li.appendChild(a);
	ul.appendChild(li);
	return li;
}

function populate() {
	Person.values.sort(Person.sortByName);
	var people = document.getElementById('people');
	for (var i = 0; i < Person.values.length; i++) {
		makeItemLink(Person.values[i], people);
	}
}

function log() {
	if (false) return;
	for (var i = 0; i < arguments.length; i++) document.writeln(arguments[i]);
}

function Person(id, name, born, died) {
	this.id = id;
	if (Person.index[id]) {
		log('Duplicate id value ' + id + ' for ' + name);
	} else {
		Person.index[id] = this;
	}
	Person.values.push(this);
	this.Name = name;
	this.Born = born;
	this.Died = died;
	this.parents = [];
	this.children = [];
	this.spouses = [];
}
Person.index = {};
Person.values = [];
Person.sortByName = function(a, b) {
	return (a.Name == b.Name) ? 0 : ((a.Name < b.Name) ? -1 : 1);
}
Person.prototype.toString = function() {
	var s = this.Name;
	if (this.Born) {
		if (this.Died) {
			s += ' (' + this.Born.substr(0,4) + ' - ' + this.Died.substr(0,4) + ')';
		} else {
			s += ' (' + this.Born.substr(0,4) + ' - )';
		}
	} else if (this.Died) {
		s += ' ( - ' + this.Died.substr(0,4) + ')';
	}
	return s;
}
Person.prototype.addSpouse = function(id) {
	var who = Person.index[id];
	if (this === who) {
		log('Self added as spouse ' + this);
	}
	this.spouses.push(who);
	who.spouses.push(this);
	return this;
}
Person.prototype.addParents = function(id1, id2) {
	var a = Person.index[id1];
	var b = Person.index[id2];
	if (a) {
		if (a === this) {
			log('Self added as parent ' + this);
		}
		a.children.push(this);
		this.parents.push(a);
	}
	if (b) {
		if (b === this) {
			log('Self added as parent ' + this);
		}
		b.children.push(this);
		this.parents.push(b);
	}
	return this;
}
Person.prototype.getSiblings = function() {
	// Collect all children of my parents
	var children = [];
	for (var i = 0; i < this.parents.length; i++) {
		children = children.concat(this.parents[i].children);
	}
	// Eliminate duplicates and self
	var siblings = {};
	for (var i = 0; i < children.length; i++) {
		var child = children[i];
		if (child === this) continue;
		siblings[child.id] = child;
	}
	// HACK START
	// this is used by getCousins
	this.siblings = siblings;
	// HACK END
	// Return result array
	var result = [];
	for (id in siblings) {
		result.push(siblings[id]);
	}
	return result;
}
Person.prototype.getCousins = function() {
	// Collect all my grandparents
	var grandparents = [];
	for (var i = 0; i < this.parents.length; i++) {
		grandparents = grandparents.concat(this.parents[i].parents);
	}
	// Collect all grandchildren of my grandparents
	var grandchildren = [];
	for (var i = 0; i < grandparents.length; i++) {
		for (var j = 0; j < grandparents[i].children.length; j++) {
			grandchildren = grandchildren.concat(grandparents[i].children[j].children);
		}
	}
	// Eliminate duplicates, self and siblings
	var cousins = {};
	for (var i = 0; i < grandchildren.length; i++) {
		var grandchild = grandchildren[i];
		if (grandchild === this) continue;
		if (this.siblings[grandchild.id]) continue;
		cousins[grandchild.id] = grandchild;
	}
	// Return result array
	var result = [];
	for (id in cousins) {
		result.push(cousins[id]);
	}
	return result;
}

function Csv() {}
Csv.prototype.readNode = function(id) {
	var node = document.getElementById(id);
	this.readText('' + node.innerText);
}
Csv.prototype.readText = function(text) {
	this.text = text;
	this.index = 0;
	this.rows = [];
	var row;
	while (row = this.nextRow()) {
		this.rows.push(row);
	}
}
Csv.prototype.nextRow = function() {
	var row = [];
	var field;
	while (field = this.nextField()) {
		row.push(field);
	}
	return (row.length == 0) ? null : row;
}
Csv.prototype.nextField = function() {
	if (this.index >= this.text.length) {
		return null;
	} else if (this.eoln) {
		this.eoln = false;
		return null;
	}
	var value = '';
	var inQuote = false;
	var c;
	while (c = this.getChar()) {
		this.index++;
		if (inQuote) {
			if ('"' == c) {
				if ('"' == this.getChar()) {
					value += c;
					this.index++;
				} else {
					inQuote = false;
				}
			} else {
				value += c;
			}
		} else {
			switch (c) {
			case '"':
				inQuote = true;
				break;
			case ',':
				return new Csv.Field(value);
			case '\r':
			case '\n':
				this.skipCrLf();
				this.eoln = true;
				return new Csv.Field(value);
			default:
				value += c;
				break;
			}
		}
	}
	return new Csv.Field(value);
}
Csv.prototype.getChar = function() {
	if (this.index >= this.text.length) {
		return false;
	}
	return this.text.charAt(this.index);
}
Csv.prototype.skipCrLf = function() {
	while (this.index < this.text.length) {
		switch (this.getChar()) {
		case '\r':
		case '\n':
			this.index++;
			break;
		default:
			return;
		}
	}
}
Csv.prototype.toString = function() {
	var result = '';
	for (var i = 0; i < this.rows.length; i++) {
		result += this.rows[i].join(',') + '\n';
	}
	return result;
}
Csv.Field = function(value) {
	this.value = value;
}
Csv.Field.prototype.toString = function() {
	var v = '' + this.value;
	if (/[",\n\r]/.test(v)) {
		return '"' + v.replace(/"/g, '""') + '"';
	} else {
		return v;
	}
}


new Person(1, 'Great Grandmother');
new Person(2, 'Great Grandfather').addSpouse(1);
new Person(3, 'Maternal Grandmother').addParents(1,2);
new Person(4, 'Maternal Grandfather').addSpouse(3);
new Person(5, 'Paternal Grandmother');
new Person(6, 'Paternal Grandfather').addSpouse(5);
new Person(7, 'Great Uncle').addParents(1,2);
new Person(8, 'Great Aunt').addSpouse(7);
new Person(9, 'Mother').addParents(3,4);
new Person(10, 'Father').addSpouse(9).addParents(5,6);
new Person(11, 'Step Father').addSpouse(9);
new Person(12, 'Maternal Uncle').addParents(3,4);
new Person(13, 'Maternal Aunt').addSpouse(12);
new Person(14, 'Paternal Uncle').addParents(5,6);
new Person(15, 'Paternal Aunt').addParents(5,6);
new Person(16, 'Mother-in-law');
new Person(17, 'Father-in-law').addSpouse(16);
new Person(18, 'Me').addParents(9,10);
new Person(19, 'Spouse').addSpouse(18).addParents(16,17);
new Person(20, 'Sister').addParents(9,10);
new Person(21, 'Brother').addParents(9,10);
new Person(22, 'Half Sister').addParents(9,11);
new Person(23, 'Step Brother').addParents(11);
new Person(24, 'Sister-in-law').addSpouse(23);
new Person(25, 'Brother-in-law').addParents(16,17);
new Person(26, 'First Cousin').addParents(12,13);
new Person(34, 'First Cousin Once Removed').addParents(7,8);
new Person(27, 'Second Cousin').addParents(34);
new Person(28, 'Son').addParents(18,19);
new Person(29, 'Daughter').addParents(18,19);
new Person(35, 'Son-in-law').addSpouse(29);
new Person(30, 'Niece').addParents(23,24);
new Person(31, 'Nephew').addParents(20);
new Person(32, 'Granddaughter').addParents(29,35);
new Person(33, 'Grandson').addParents(29,35);

populate();
show(1);
</script></pre>
</body></html>
